package org.openkanban.client.ui.board;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import org.openkanban.client.ui.presenter.CreateActivityRequestCallback;
import org.openkanban.client.ui.presenter.GetActivityByIdRequestCallback;
import org.openkanban.client.ui.presenter.MoveKanbanToActivityRequestCallback;
import org.openkanban.client.ui.presenter.PreparedRequest;
import org.openkanban.client.ui.presenter.RemoveActivityRequestCallback;
import org.openkanban.client.ui.presenter.UpdateActivityRequestCallback;
import org.openkanban.client.util.RootPanel;
import org.openkanban.client.util.StringUtils;
import org.openkanban.shared.dto.ActivityDto;
import org.openkanban.shared.dto.KanbanDto;

import com.google.gwt.http.client.RequestCallback;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.types.Overflow;
import com.smartgwt.client.util.BooleanCallback;
import com.smartgwt.client.util.EventHandler;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.Label;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.events.DoubleClickEvent;
import com.smartgwt.client.widgets.events.DoubleClickHandler;
import com.smartgwt.client.widgets.events.DropEvent;
import com.smartgwt.client.widgets.events.DropHandler;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.fields.TextItem;
import com.smartgwt.client.widgets.form.fields.events.BlurEvent;
import com.smartgwt.client.widgets.form.fields.events.BlurHandler;
import com.smartgwt.client.widgets.form.fields.events.FocusEvent;
import com.smartgwt.client.widgets.form.fields.events.FocusHandler;
import com.smartgwt.client.widgets.form.fields.events.KeyPressEvent;
import com.smartgwt.client.widgets.form.fields.events.KeyPressHandler;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.Layout;
import com.smartgwt.client.widgets.layout.VLayout;

/**
 * GUI Element for the ActivityView
 */
public class ActivityView extends VLayout {

    public static final String DROPTYPE_INACTIVE = "inactive_activity";
    public static final String DROPTYPE_ACTIVE = "active_activity";
    private static final String DEFAULT_ACTIVITY_NAME_TEXT = "activity-name";
    private static final String DEFAULT_WIP_LIMIT_TEXT = "wip-limit";
    private static final int HEADER_HEIGHT = 24;
    private static final int MIN_ACTIVITY_WIDTH = 200;
    private static final int WIP_LIMIT_WIDTH = 60;

    private IButton cancelButton;
    private IButton deleteButton;

    private Label activityNameLabel;
    private DynamicForm activityNameForm;
    private TextItem activityNameTextItem;

    private Label activityWipLimitLabel;
    private DynamicForm activityWipLimitForm;
    private TextItem activityWipLimitTextItem;

    private HLayout activityHeaderHLayout;
    private VLayout kanbanContainerVLayout;

    private Long parentBoardId;
    private ActivityDto activityDto;

    private Map<Long, KanbanView> kanbanViewMap;
    private boolean canAddKanban;
    private MoveKanbanToActivityRequestCallback moveKanbanToActivityRequestCallback;
    private int dragDropIndex;

    public ActivityView(Long parentBoardId) {
	this.parentBoardId = parentBoardId;

	kanbanViewMap = new HashMap<Long, KanbanView>();

	moveKanbanToActivityRequestCallback = new MoveKanbanToActivityRequestCallback();

	// components
	cancelButton = new OpenKanbanButton("cancel");
	cancelButton.setAutoFit(true);
	cancelButton.addClickHandler(cancelButtonClickHandler(this));

	deleteButton = new OpenKanbanButton("x");
	deleteButton.setAutoFit(true);
	deleteButton.setVisible(false);
	deleteButton.addClickHandler(deleteButtonClickHandler(this));

	activityNameLabel = new Label();
	activityNameLabel.setStyleName("labelActivity");
	activityNameLabel.setHeight(HEADER_HEIGHT + 10);
	activityNameLabel.setWidth("*");
	activityNameLabel.setCanDropBefore(false);
	activityNameLabel.addDoubleClickHandler(activityNameLabelDoubleClickHandler());

	activityNameForm = new DynamicForm();

	activityNameTextItem = new TextItem();
	activityNameTextItem.setShowTitle(false);
	activityNameTextItem.setValue(DEFAULT_ACTIVITY_NAME_TEXT);
	activityNameTextItem.setHeight(HEADER_HEIGHT - 2);
	activityNameTextItem.setShowFocused(true);
	activityNameTextItem.setTextBoxStyle("labelActivity");
	activityNameTextItem.setAlign(Alignment.CENTER);
	activityNameTextItem.addKeyPressHandler(activityTextItemKeyPressHandler(this, activityNameTextItem, activityNameLabel,
		activityNameForm, 0));
	activityNameTextItem
		.addBlurHandler(activityTextItemBlurHandler(this, activityNameTextItem, activityNameLabel, activityNameForm, 0));
	activityNameTextItem.addFocusHandler(new FocusHandler() {
	    @Override
	    public void onFocus(FocusEvent event) {
		if (DEFAULT_ACTIVITY_NAME_TEXT.equals(activityNameTextItem.getValueAsString())) {
		    activityNameTextItem.clearValue();
		}
	    }
	});

	activityNameForm.setHeight(HEADER_HEIGHT);
	activityNameForm.setFields(activityNameTextItem);

	activityWipLimitLabel = new Label();
	activityWipLimitLabel.setShowFocused(true);
	activityWipLimitLabel.setStyleName("labelActivity");
	activityWipLimitLabel.setHeight(HEADER_HEIGHT + 10);
	activityWipLimitLabel.setWidth(WIP_LIMIT_WIDTH);
	activityWipLimitLabel.addDoubleClickHandler(activityWipLimitLabelDoubleClickHandler());

	activityWipLimitForm = new DynamicForm();

	activityWipLimitTextItem = new TextItem();
	activityWipLimitTextItem.setShowTitle(false);
	activityWipLimitTextItem.setValue(DEFAULT_WIP_LIMIT_TEXT);
	activityWipLimitTextItem.setWidth(WIP_LIMIT_WIDTH);
	activityWipLimitTextItem.setHeight(HEADER_HEIGHT - 2);
	activityWipLimitTextItem.setTextBoxStyle("labelActivity");
	activityWipLimitTextItem.addKeyPressHandler(activityTextItemKeyPressHandler(this, activityWipLimitTextItem, activityWipLimitLabel,
		activityWipLimitForm, 2));
	activityWipLimitTextItem.addBlurHandler(activityTextItemBlurHandler(this, activityWipLimitTextItem, activityWipLimitLabel,
		activityWipLimitForm, 2));
	activityWipLimitTextItem.addFocusHandler(new FocusHandler() {
	    @Override
	    public void onFocus(FocusEvent event) {
		if (DEFAULT_WIP_LIMIT_TEXT.equals(activityWipLimitTextItem.getValueAsString())) {
		    activityWipLimitTextItem.clearValue();
		}
	    }
	});

	activityWipLimitForm.setWidth(WIP_LIMIT_WIDTH);
	activityWipLimitForm.setHeight(HEADER_HEIGHT);
	activityWipLimitForm.setFields(activityWipLimitTextItem);

	activityHeaderHLayout = new HLayout();
	activityHeaderHLayout.setHeight(HEADER_HEIGHT);
	activityHeaderHLayout.setStyleName("activityHeaderHLayout");

	Layout placeHolderLayout = new Layout();
	placeHolderLayout.setWidth("*");
	activityHeaderHLayout.setCanDropBefore(false);
	activityHeaderHLayout.addMember(activityNameForm);
	activityHeaderHLayout.addMember(placeHolderLayout);
	activityHeaderHLayout.addMember(activityWipLimitForm);
	activityHeaderHLayout.addMember(cancelButton);
	activityHeaderHLayout.addMember(deleteButton);

	kanbanContainerVLayout = new VLayout();
	kanbanContainerVLayout.setScrollbarSize(9);
	kanbanContainerVLayout.setOverflow(Overflow.AUTO);
	kanbanContainerVLayout.setDropTypes("kanban");
	kanbanContainerVLayout.setCanAcceptDrop(true);
	kanbanContainerVLayout.setAnimateMembers(true);
	kanbanContainerVLayout.setAnimateMemberTime(666);
	kanbanContainerVLayout.setShowDragPlaceHolder(false);

	// activity events
	kanbanContainerVLayout.addDropHandler(new DropHandler() {
	    @Override
	    public void onDrop(DropEvent event) {
		KanbanView target = (KanbanView) EventHandler.getDragTarget();
		Long sourceActivityId = target.getParentActivityId();
		Long targetActivityId = activityDto.getActivityId();

		int dragDiff = getDragDifference(sourceActivityId, targetActivityId);

		boolean dragOK = true;

		if (isInvalidTarget(dragDiff)) {
		    dragOK = false;
		    event.cancel();
		} else if (isForwardDirection(dragDiff) && !target.isReadyForPull()) {
		    dragOK = false;
		    event.cancel();
		} else if (activityLimitAcceeded()) {
		    dragOK = false;
		    event.cancel();
		    SC.say("Kanban nicht verschoben werden: Work-In-Progress Limit erreicht.");
		}

		if (dragOK) {
		    target.setParentActivityId(activityDto.getActivityId());
		    target.getKanbanDto().setPullable(!target.getKanbanDto().getPullable());
		    if (isFirstActivityView()) {
			target.getKanbanDto().setPullable(true);
		    } else {
			target.getKanbanDto().setPullable(false);
		    }
		    target.togglePullableButton();

		    ActivityView sourceActivityView = RootPanel.main.getBoard().getWhiteBoardView().getActivityViewById(sourceActivityId);
		    sourceActivityView.getKanbanViewMap().remove(target.getKanbanDto().getKanbanId());
		    kanbanViewMap.put(target.getKanbanDto().getKanbanId(), target);

		    moveKanbanToActivityRequestCallback.setSourceActivityId(sourceActivityId);
		    moveKanbanToActivityRequestCallback.setTargetActivityId(targetActivityId);

		    PreparedRequest prepReq = new PreparedRequest("activity", activityDto.getActivityId() + "/kanban/" + target.getTitle()
			    + "/move");

		    prepReq.sendAsyncRequest(moveKanbanToActivityRequestCallback);
		}
	    }

	    private boolean activityLimitAcceeded() {
		return activityDto.getWipLimit() <= kanbanContainerVLayout.getMembers().length;
	    }

	    private boolean isInvalidTarget(int dragDiff) {
		return dragDiff > 1 || dragDiff == 0;
	    }

	    private boolean isForwardDirection(int dragDiff) {
		return dragDiff == 1;
	    }
	});

	// activity properties
	setMargin(5);
	setStyleName("activity");
	setWidth("*");
	setMinWidth(MIN_ACTIVITY_WIDTH);
	setHeight100();

	// drop line style
	Canvas dropLineProperties = new Canvas();
	dropLineProperties.setBackgroundColor("#99ddee");
	kanbanContainerVLayout.setDropLineProperties(dropLineProperties);
	kanbanContainerVLayout.setDropLineThickness(1);

	// add members
	addMember(activityHeaderHLayout);
	addMember(kanbanContainerVLayout);
    }

    public ActivityView(ActivityDto activityDto, Long parentBoardId) {
	this(parentBoardId);
	this.activityDto = activityDto;
	this.activityNameLabel.setContents(activityDto.getActivityName());
	this.activityNameTextItem.setValue(activityDto.getActivityName());
	this.activityWipLimitLabel.setContents(Integer.toString(activityDto.getWipLimit()));
	this.activityWipLimitTextItem.setValue(Integer.toString(activityDto.getWipLimit()));

	hideEditActivityName();
	hideEditWipLimit();
	hideCancelButton();
	showDeleteButton();

	for (KanbanDto kanbanDto : activityDto.getKanbans()) {
	    addKanban(kanbanDto);
	}
    }

    public KanbanView addKanban(KanbanDto kanbanDto) {
	if (!kanbanViewExists(kanbanDto.getKanbanId())) {
	    KanbanView kanbanView = new KanbanView(kanbanDto, activityDto.getActivityId());
	    kanbanViewMap.put(kanbanDto.getKanbanId(), kanbanView);
	    this.kanbanContainerVLayout.addMember(kanbanView);
	    return kanbanView;
	}
	return null;
    }

    private int getDragDifference(Long sourceActivityId, Long targetActivityId) {
	List<ActivityView> activityViewList = RootPanel.main.getBoard().getWhiteBoardView().getActivityViewList();

	TreeSet<Long> activityIdSet = new TreeSet<Long>();
	for (ActivityView activityView : activityViewList) {
	    activityIdSet.add(activityView.getActivityDto().getActivityId());
	}

	Long[] activityIdArray = activityIdSet.toArray(new Long[] {});

	int sourceIndex = 0;

	for (int i = 0; i < activityIdArray.length; i++) {
	    Long id = activityIdArray[i];
	    if (id.equals(sourceActivityId)) {
		sourceIndex = i;
	    }
	}

	int targetIndex = 0;

	for (int i = 0; i < activityIdArray.length; i++) {
	    Long id = activityIdArray[i];
	    if (id.equals(targetActivityId)) {
		targetIndex = i;
	    }
	}

	return targetIndex - sourceIndex;
    }

    public boolean isFirstActivityView() {
	return activityDto.getActivityId() == RootPanel.main.getBoard().getWhiteBoardView().getFirstActicityDto().getActivityId();
    }

    private void showEditActivityName() {
	activityHeaderHLayout.removeMember(activityNameLabel);
	activityHeaderHLayout.addMember(activityNameForm, 0);
	activityNameTextItem.focusInItem();
    }

    private void hideEditActivityName() {
	activityHeaderHLayout.removeMember(activityNameForm);
	activityHeaderHLayout.addMember(activityNameLabel, 0);
    }

    private void showEditWipLimit() {
	activityHeaderHLayout.removeMember(activityWipLimitLabel);
	activityHeaderHLayout.addMember(activityWipLimitForm, 2);
	activityWipLimitTextItem.focusInItem();
    }

    private void hideEditWipLimit() {
	activityHeaderHLayout.removeMember(activityWipLimitForm);
	activityHeaderHLayout.addMember(activityWipLimitLabel, 2);
    }

    public void hideCancelButton() {
	if (activityHeaderHLayout.contains(cancelButton)) {
	    activityHeaderHLayout.removeMember(cancelButton);
	}
    }

    public void showDeleteButton() {
	deleteButton.setVisible(true);
    }

    private ClickHandler cancelButtonClickHandler(final ActivityView activityView) {
	return new ClickHandler() {
	    @Override
	    public void onClick(ClickEvent event) {
		RootPanel.main.getBoard().enableNewActivityButton();
		RootPanel.main.getBoard().getWhiteBoardView().removeActivity(activityView);
	    }
	};
    }

    private ClickHandler deleteButtonClickHandler(final ActivityView activityView) {
	return new ClickHandler() {
	    @Override
	    public void onClick(ClickEvent event) {
		SC.confirm("Do you want to remove the activity \"" + activityView.getActivityDto().getActivityName() + "\" from Board?",
			new BooleanCallback() {
			    @Override
			    public void execute(Boolean value) {
				if (value != null && value) {
				    Long activityId = activityView.getActivityDto().getActivityId();
				    PreparedRequest prepReq = new PreparedRequest("activity", activityId.toString() + "/remove");
				    prepReq.sendAsyncRequest(new RemoveActivityRequestCallback(activityView));
				}
			    }
			});
	    }
	};
    }

    private DoubleClickHandler activityNameLabelDoubleClickHandler() {
	return new DoubleClickHandler() {
	    @Override
	    public void onDoubleClick(DoubleClickEvent event) {
		activityNameTextItem.setValue(activityNameLabel.getContents());

		showEditActivityName();
	    }
	};
    }

    private DoubleClickHandler activityWipLimitLabelDoubleClickHandler() {
	return new DoubleClickHandler() {
	    @Override
	    public void onDoubleClick(DoubleClickEvent event) {
		activityWipLimitTextItem.setValue(activityWipLimitLabel.getContents());
		showEditWipLimit();
	    }
	};
    }

    private KeyPressHandler activityTextItemKeyPressHandler(final ActivityView activityView, final TextItem textItem, final Label label,
	    final DynamicForm form, final int position) {
	return new KeyPressHandler() {
	    @Override
	    public void onKeyPress(KeyPressEvent event) {
		if ("Enter".equals(event.getKeyName())) {
		    textItem.blurItem();
		}
	    }
	};
    }

    private BlurHandler activityTextItemBlurHandler(final ActivityView activityView, final TextItem textItem, final Label label,
	    final DynamicForm form, final int position) {
	return new BlurHandler() {
	    @Override
	    public void onBlur(BlurEvent event) {
		updateActivityData(activityView, textItem, label, form, position);
	    }
	};
    }

    public void updateActivityDto() {
	PreparedRequest prepReq = new PreparedRequest("activity", activityDto.getActivityId().toString());
	prepReq.sendAsyncRequest(new GetActivityByIdRequestCallback(this));
    }

    private void updateActivityData(ActivityView activityView, TextItem textItem, Label label, DynamicForm form, int position) {
	label.setContents(textItem.getValueAsString());

	if (activityView.getActivityHeaderHLayout().contains(form)) {
	    activityView.getActivityHeaderHLayout().removeMember(form);
	    activityView.getActivityHeaderHLayout().addMember(label, position);
	}

	PreparedRequest prepReq = null;
	RequestCallback requestCallback = null;
	String context = "";
	String ressource = "";
	if (activityView.getActivityDto() != null) {
	    context = "activity";
	    ressource = "update";
	    requestCallback = new UpdateActivityRequestCallback(activityView);
	} else {
	    activityView.setActivityDto(new ActivityDto());
	    context = "board";
	    ressource = activityView.getParentBoardId().toString() + "/activity/add";
	    requestCallback = new CreateActivityRequestCallback(activityView);
	}
	String wipLimitString = activityView.getActivityWipLimitTextItem().getValueAsString();
	String activityName = activityView.getActivityNameTextItem().getValueAsString();

	activityView.getActivityDto().setActivityName(activityName);
	if (StringUtils.isNumeric(wipLimitString)) {
	    activityView.getActivityDto().setWipLimit(Integer.parseInt(wipLimitString));
	}

	prepReq = new PreparedRequest(context, ressource, activityView.getActivityDto());
	prepReq.sendAsyncRequest(requestCallback);
    }

    public void removeKanbanView(Long kanbanId) {
	kanbanContainerVLayout.removeMember(kanbanViewMap.get(kanbanId));
	kanbanViewMap.remove(kanbanId);
    }

    public boolean kanbanViewExists(Long kanbanId) {
	return kanbanViewMap.containsKey(kanbanId);
    }

    public boolean canAddKanban() {
	return this.canAddKanban;
    }

    public VLayout getKanbanContainerVLayout() {
	return kanbanContainerVLayout;
    }

    public ActivityDto getActivityDto() {
	return activityDto;
    }

    public void setActivityDto(ActivityDto activityDto) {
	this.activityDto = activityDto;
	activityNameLabel.setContents(activityDto.getActivityName());
	activityWipLimitLabel.setContents(Integer.toString(activityDto.getWipLimit()));
    }

    public Label getActivityNameLabel() {
	return activityNameLabel;
    }

    public TextItem getActivityNameTextItem() {
	return activityNameTextItem;
    }

    public HLayout getActivityHeaderHLayout() {
	return activityHeaderHLayout;
    }

    public DynamicForm getActivityNameForm() {
	return activityNameForm;
    }

    public Long getParentBoardId() {
	return parentBoardId;
    }

    public TextItem getActivityWipLimitTextItem() {
	return activityWipLimitTextItem;
    }

    public int getDragDropIndex() {
	return dragDropIndex;
    }

    public void setDragDropIndex(int dragDropIndex) {
	this.dragDropIndex = dragDropIndex;
    }

    public Map<Long, KanbanView> getKanbanViewMap() {
	return kanbanViewMap;
    }

}